#include <linux/init.h>
#include <linux/tty.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/export.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/slab.h>

#define   MM_SIZE   1024 * 8
#define   MIN(x,y)  (x < y ? x : y)

static int major;
static char *buf = NULL;
struct class * hello_class;


static int hello_open (struct inode *node, struct file *filp)
{
	printk("hello_open\n");
	printk("%s %s %d\n",__FILE__, __FUNCTION__, __LINE__);
	return 0;
}

static ssize_t hello_read (struct file *filp, char  *buff, size_t size, loff_t *offset)
{
	int err;
	err = copy_to_user(buff, buf, MIN(MM_SIZE, size));
	printk("hello_read\n");
	return MIN(MM_SIZE, size);
}

static ssize_t hello_write (struct file *filp, const char  *buf, size_t size, loff_t *offset)
{
	printk("hello_write\n");
	return size;
}

static int hello_release (struct inode *node, struct file *filp)
{
	printk("hello_release\n");
	return 0;
}

static int hello_mmap (struct file *filp, struct vm_area_struct *vma)
{	
	/*设置属性: cache，buffer */
	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

	if(remap_pfn_range(vma,//虚拟内存区域，即设备地址将要映射到这里  
                       vma->vm_start,//虚拟空间的起始地址  
                       virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号，物理地址右移12位  
                       vma->vm_end - vma->vm_start,//映射区域大小，一般是页大小的整数倍  
                       vma->vm_page_prot))//保护属性，  
    {  
        return -EAGAIN;  
    }  
    return 0;
}

/*1.定义 file_operations 结构体*/
static const struct file_operations hello_drv = {
    .owner 		= THIS_MODULE,
	.read		= hello_read,
	.write		= hello_write,
	.open		= hello_open,
	.release    = hello_release,
	.mmap 		= hello_mmap,
};


/*2.register_chrdev*/

/*3.入口函数*/
static int hello_init(void)
{
	//内核申请内存只能按页申请，申请该内存以便后面把它当作虚拟设备
	buf = kmalloc(MM_SIZE, GFP_KERNEL);

	strcpy(buf, "abc");
	
	//设备号
	major = register_chrdev(0,"hello_drv",&hello_drv);

	hello_class = class_create(THIS_MODULE, "hello_class");

	device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello");
	
	return 0;
}

/*4.退出函数*/
static void hello_exit(void)
{
	kfree(buf);
	device_destroy(hello_class, MKDEV(major, 0));
	class_destroy(hello_class);
	unregister_chrdev(major,"hello_drv");
}	

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
